﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using System.Threading;
using GoodStuff.Web;

namespace GoodStuff.UnitTests
{
    [TestFixture]
    public class CacheUtilityTest
    {
        [SetUp]
        public void Setup()
        {
            Cached.RemoveAll();
        }

        [Test]
        public void MakeSureTwoCallsAreCached()
        {          
            CallCounter counter = new CallCounter();

            int results = Cached.Get<CounterAsObject>("key1", "group", TimeSpan.FromSeconds(2), () => counter.WaitAndCall()).Value;
            int result2 = Cached.Get<CounterAsObject>("key1", "group", TimeSpan.FromSeconds(2), () => counter.WaitAndCall()).Value;

            Assert.AreEqual(1, counter.counter);
        }

        [Test]
        public void WithDifferentKeysTheMethodShouldBeCalledTwice()
        {
            CallCounter counter = new CallCounter();

            int results = Cached.Get<CounterAsObject>("key1", "group", TimeSpan.FromSeconds(2), () => counter.WaitAndCall()).Value;
            int result2 = Cached.Get<CounterAsObject>("key2", "group", TimeSpan.FromSeconds(2), () => counter.WaitAndCall()).Value;

            Assert.AreEqual(2, counter.counter);
        }

        [Test]
        public void IfIStart10ThreadsThatWaitOneSecondTheTotalOperationShouldBeDoneWithin2Seconds()
        {
            DateTime now = DateTime.Now;

            Thread[] threads = new Thread[50];
            for (int i = 0; i < threads.Length; i++)
            {
                Sleeper p = new Sleeper();
                ThreadStart ts = new ThreadStart(p.Invoke);
                Thread t = new Thread(ts);
                threads[i] = t;
                t.Start();
            }

            for (int i = 0; i < threads.Length; i++)
            {
                threads[i].Join();
            }

            Assert.IsTrue(DateTime.Now - now < TimeSpan.FromSeconds(2));
        }

        [Test]
        public void FiftyCallsThroughTheCacheUtilityShouldNotBlock()
        {
            DateTime now = DateTime.Now;

            Thread[] threads = new Thread[50];
            for (int i = 0; i < threads.Length; i++)
            {
                Sleeper p = new Sleeper();
                ThreadStart ts = new ThreadStart(p.InvokeCached);
                Thread t = new Thread(ts);
                threads[i] = t;
                t.Start();
            }

            for (int i = 0; i < threads.Length; i++)
            {
                threads[i].Join();
            }

            Assert.IsTrue(DateTime.Now - now < TimeSpan.FromSeconds(2));
        }

        [Test]
        public void FiftyCallsThroughTheCacheUtilityShouldNotBlockWithSameKey()
        {
            DateTime now = DateTime.Now;

            Sleeper p = new Sleeper();
                
            Thread[] threads = new Thread[50];
            for (int i = 0; i < threads.Length; i++)
            {
                ThreadStart ts = new ThreadStart(p.InvokeCachedSameKey);
                Thread t = new Thread(ts);
                threads[i] = t;
                t.Start();
            }

            for (int i = 0; i < threads.Length; i++)
            {
                threads[i].Join();
            }

            Assert.IsTrue(DateTime.Now - now < TimeSpan.FromSeconds(2));
            Assert.AreEqual(1, p.NumberOfInvokes);
        }

        [Test]
        public void CachingANullValueShouldBeOk()
        {
            CallCounter counter = new CallCounter();

            var results = Cached.Get<CounterAsObject>("key1", "group", TimeSpan.FromSeconds(2), () => counter.ReturnNull() );
            var result2 = Cached.Get<CounterAsObject>("key1", "group", TimeSpan.FromSeconds(2), () => counter.ReturnNull() );

            Assert.AreEqual(1, counter.counter);
        }

        [Test]
        public void IWantToCacheAnInt()
        {
            CallCounter counter = new CallCounter();

            var results = Cached.Get<int>("key1", "group", TimeSpan.FromSeconds(2), () => counter.ReturnInteger());
            var result2 = Cached.Get<int>("key1", "group", TimeSpan.FromSeconds(2), () => counter.ReturnInteger());

            Assert.AreEqual(1, counter.counter);
        }

        private class Sleeper
        {
            private static int keyCounter = 1;

            public int NumberOfInvokes = 0;

            public void Invoke()
            {
                NumberOfInvokes++;
                Thread.Sleep(TimeSpan.FromSeconds(1));                
            }

            public void InvokeCached()
            {
                Cached.Get<CounterAsObject>("key" + keyCounter++, "group", TimeSpan.FromSeconds(2), () => { Invoke(); return new CounterAsObject(); });
            }

            public void InvokeCachedSameKey()
            {
                Cached.Get<CounterAsObject>("key", "group", TimeSpan.FromSeconds(2), () => { Invoke(); return new CounterAsObject(); });
            }

            public void InvokeSomeReader()
            {
            }
        }

        private class CallCounter
        {
            public int counter { get; set; }

            public CounterAsObject WaitAndCall()
            {
                System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(200));
                counter++;
                return new CounterAsObject() { Value = counter };
            }

            public int ReturnInteger()
            {
                counter++;
                return counter;
            }

            public CounterAsObject ReturnNull()
            {
                counter++;
                return null;
            }
        }

        public class CounterAsObject
        {
            public int Value { get; set; }
        }
    }
}
